using System;
using System.Collections.Generic;
using System.Linq;

using Server.Engines.PartySystem;
using Server.Guilds;
using Server.Items;
using Server.Misc;
using Server.Mobiles;
using Server.Multis;
using Server.Regions;
using Server.Services.Virtues;
using Server.Spells.Fifth;
using Server.Spells.Necromancy;
using Server.Spells.Ninjitsu;
using Server.Spells.Seventh;
using Server.Spells.Fourth;
using Server.Targeting;
using Server.Spells.SkillMasteries;
using Server.Spells.Spellweaving;

namespace Server
{
    public class DefensiveSpell
    {
        public static void Nullify(Mobile from)
        {
            if (!from.CanBeginAction(typeof(DefensiveSpell)))
                new InternalTimer(from).Start();
        }

        private class InternalTimer : Timer
        {
            private readonly Mobile m_Mobile;

            public InternalTimer(Mobile m)
                : base(TimeSpan.FromMinutes(1.0))
            {
                m_Mobile = m;

                Priority = TimerPriority.OneSecond;
            }

            protected override void OnTick()
            {
                m_Mobile.EndAction(typeof(DefensiveSpell));
            }
        }
    }
}

namespace Server.Spells
{
    public enum TravelCheckType
    {
        RecallFrom,
        RecallTo,
        GateFrom,
        GateTo,
        Mark,
        TeleportFrom,
        TeleportTo
    }

    public class SpellHelper
    {
        #region Spell Focus and SDI Calculations
        private static SkillName[] _Schools =
        {
            SkillName.Magery,
            SkillName.AnimalTaming,
            SkillName.Musicianship,
            SkillName.Mysticism,
            SkillName.Spellweaving,
            SkillName.Chivalry,
            SkillName.Necromancy,
            SkillName.Bushido,
            SkillName.Ninjitsu
        };

        private static SkillName[] _TOLSchools =
        {
            SkillName.Magery,
            SkillName.AnimalTaming,
            SkillName.Musicianship,
            SkillName.Mysticism,
            SkillName.Spellweaving,
            SkillName.Chivalry,
            SkillName.Necromancy,
            SkillName.Bushido,
            SkillName.Ninjitsu,
            SkillName.Parry
        };

        public static bool HasSpellFocus(Mobile m, SkillName focus)
        {
            SkillName[] list = Core.TOL ? _TOLSchools : _Schools;

            foreach (SkillName skill in list)
            {
                if (skill != focus && m.Skills[skill].Value >= 30.0)
                    return false;
            }

            return true;
        }

        public static int PvPSpellDamageCap(Mobile m, SkillName castskill)
        {
            if (!Core.SA)
                return 15;

            if (HasSpellFocus(m, castskill))
            {
                return 30;
            }
            else
            {
                return Core.TOL ? 20 : 15;
            }
        }

        public static int GetSpellDamageBonus(Mobile caster, IDamageable damageable, SkillName skill, bool playerVsPlayer)
        {
            Mobile target = damageable as Mobile;

            int sdiBonus = AosAttributes.GetValue(caster, AosAttribute.SpellDamage);

            if (target != null)
            {
                if (RunedSashOfWarding.IsUnderEffects(target, WardingEffect.SpellDamage))
                    sdiBonus -= 10;

                sdiBonus -= Block.GetSpellReduction(target);
            }

            // PvP spell damage increase cap of 15% from an item’s magic property, 30% if spell school focused.
            if (Core.SE && playerVsPlayer)
            {
                sdiBonus = Math.Min(sdiBonus, PvPSpellDamageCap(caster, skill));
            }

            return sdiBonus;
        }
        #endregion 

        private static readonly TimeSpan AosDamageDelay = TimeSpan.FromSeconds(1.0);
        private static readonly TimeSpan OldDamageDelay = TimeSpan.FromSeconds(0.5);

        public static TimeSpan GetDamageDelayForSpell(Spell sp)
        {
            if (!sp.DelayedDamage)
                return TimeSpan.Zero;

            return (Core.AOS ? AosDamageDelay : OldDamageDelay);
        }

        public static bool CheckMulti(Point3D p, Map map)
        {
            return CheckMulti(p, map, true, 0);
        }
		
        public static bool CheckMulti(Point3D p, Map map, bool houses)
        {
            return CheckMulti(p, map, houses, 0);
        }
		
        public static bool CheckMulti(Point3D p, Map map, bool houses, int housingrange)
        {
            if (map == null || map == Map.Internal)
                return false;

            Sector sector = map.GetSector(p.X, p.Y);

            for (int i = 0; i < sector.Multis.Count; ++i)
            {
                BaseMulti multi = sector.Multis[i];

                if (multi is BaseHouse)
                {
                    BaseHouse bh = (BaseHouse)multi;

                    if ((houses && bh.IsInside(p, 16)) || (housingrange > 0 && bh.InRange(p, housingrange)))
                        return true;
                }
                else if (multi.Contains(p))
                {
                    return true;
                }
            }
			
            return false;
        }

        public static void Turn(Mobile from, object to)
        {
            IPoint3D target = to as IPoint3D;
            int d = -1;

            if (target == null)
                return;

            if (target is Item)
            {
                Item item = (Item)target;

                if (item.RootParent != from)
                    d = (int)from.GetDirectionTo(item.GetWorldLocation());
            }
            else if (from != target)
            {
                d = (int)from.GetDirectionTo(target);
            }

            if (d > -1)
            {
                from.Direction = (Direction)d;
                from.ProcessDelta();
            }
        }

        public static bool CheckCombat(Mobile m, bool restrict = true)
        {
            if (!restrict)
                return false;

            return Aggression.CheckHasAggression(m, Core.AOS);
        }

        public static bool AdjustField(ref Point3D p, Map map, int height, bool mobsBlock)
        {
            if (map == null)
                return false;

            for (int offset = 0; offset < 25; ++offset)
            {
                Point3D loc = new Point3D(p.X, p.Y, p.Z - offset);

                if (map.CanFit(loc, height, true, mobsBlock))
                {
                    p = loc;
                    return true;
                }

                loc = new Point3D(p.X, p.Y, p.Z + offset);

                if (map.CanFit(loc, height, true, mobsBlock))
                {
                    p = loc;
                    return true;
                }
            }

            return false;
        }

        public static bool CheckField(Point3D p, Map map)
        {
            if (map == null)
                return false;

            IPooledEnumerable eable = map.GetItemsInRange(p, 0);

            foreach (Item item in eable)
            {
                Type t = item.GetType();

                if(t.IsDefined(typeof(DispellableFieldAttribute), false) || t.IsDefined(typeof(DispellableFieldAttribute), true))
                {
                    eable.Free();
                    return false;
                }
            }

            eable.Free();
            return true;
        }

        public static bool CheckWater(Point3D p, Map map)
        {
            var landTile = map.Tiles.GetLandTile(p.X, p.Y);

            if (landTile.Z == p.Z && ((landTile.ID >= 168 && landTile.ID <= 171) || (landTile.ID >= 310 && landTile.ID <= 311)))
            {
                return false;
            }

            var tiles = map.Tiles.GetStaticTiles(p.X, p.Y, true);

            foreach (var tile in tiles)
            {
                if (tile.Z == p.Z && tile.ID >= 0x1796 && tile.ID <= 0x17B2)
                {
                    return false;
                }
            }

            return true;
        }
		
        public static bool CanRevealCaster(Mobile m)
        {
            if (m is BaseCreature)
            {
                BaseCreature c = (BaseCreature)m;
						
                if (!c.Controlled)
                    return true;
            }
			
            return false;
        }

        public static void GetSurfaceTop(ref IPoint3D p)
        {
            if (p is Item)
            {
                p = ((Item)p).GetSurfaceTop();
            }
            else if (p is StaticTarget)
            {
                StaticTarget t = (StaticTarget)p;
                int z = t.Z;

                if ((t.Flags & TileFlag.Surface) == 0)
                    z -= TileData.ItemTable[t.ItemID & TileData.MaxItemValue].CalcHeight;

                p = new Point3D(t.X, t.Y, z);
            }
        }

		protected static void RemoveStatOffsetCallback(object state)
		{
			if (!(state is Mobile))
				return;
			// This call has the side-effect of updating all stats
			((Mobile)state).CheckStatTimers();
		}			

        public static bool AddStatOffset(Mobile m, StatType type, int offset, TimeSpan duration)
        {
            if (offset > 0)
                return AddStatBonus(m, m, type, offset, duration);
            else if (offset < 0)
                return AddStatCurse(m, m, type, -offset, duration);

            return true;
        }

        public static bool AddStatBonus(Mobile caster, Mobile target, bool blockSkill, StatType type)
        {
            return AddStatBonus(caster, target, type, GetOffset(caster, target, type, false, blockSkill), GetDuration(caster, target));
        }

        public static bool AddStatBonus(Mobile caster, Mobile target, StatType type, int bonus, TimeSpan duration)
        {
            int offset = bonus;
            string name = String.Format("[Magic] {0} Buff", type);

            StatMod mod = target.GetStatMod(name);

            if (mod != null)
            {
                offset = Math.Max(mod.Offset, offset);
            }

            target.AddStatMod(new StatMod(type, name, offset, duration));
			Timer.DelayCall(duration, RemoveStatOffsetCallback, target);

            return true;
        }

        public static int GetBuffOffset(Mobile m, StatType type)
        {
            string name = String.Format("[Magic] {0} Buff", type);

            StatMod mod = m.GetStatMod(name);

            return mod != null ? mod.Offset : 0;
        }

        public static bool AddStatCurse(Mobile caster, Mobile target, StatType type)
        {
            return AddStatCurse(caster, target, type, true);
        }

        public static bool AddStatCurse(Mobile caster, Mobile target, StatType type, bool blockSkill)
        {
            return AddStatCurse(caster, target, type, GetOffset(caster, target, type, true, blockSkill), TimeSpan.Zero);
        }

        public static bool AddStatCurse(Mobile caster, Mobile target, StatType type, bool blockSkill, int offset)
        {
            return AddStatCurse(caster, target, type, offset, TimeSpan.Zero);
        }

        public static bool AddStatCurse(Mobile caster, Mobile target, StatType type, int curse, TimeSpan duration)
        {
            int offset = curse;
            string name = String.Format("[Magic] {0} Curse", type);

            StatMod mod = target.GetStatMod(name);

			if (mod != null)
				offset = Math.Max(mod.Offset, offset);

			offset *= -1;

            target.AddStatMod(new StatMod(type, name, offset, TimeSpan.Zero));
			return true;
        }

        public static TimeSpan GetDuration(Mobile caster, Mobile target)
        {
            if (Core.AOS)
            {
                int span = (((6 * caster.Skills.EvalInt.Fixed) / 50) + 1);

                if (caster.Spell is CurseSpell && Spells.SkillMasteries.ResilienceSpell.UnderEffects(target))
                    span /= 2;

                return TimeSpan.FromSeconds(span);
            }

            return TimeSpan.FromSeconds(caster.Skills[SkillName.Magery].Value * 1.2);
        }

        public static int GetCurseOffset(Mobile m, StatType type)
        {
            string name = String.Format("[Magic] {0} Curse", type);

            StatMod mod = m.GetStatMod(name);

            return mod != null ? mod.Offset : 0;
        }

        public static double GetOffsetScalar(Mobile caster, Mobile target, bool curse)
        {
            double percent;

            if (curse)
            {
                double resistFixed = target.Skills.MagicResist.Fixed - (EvilOmenSpell.GetResistMalus(target) * 10);
                percent = 8 + (caster.Skills.EvalInt.Fixed / 100) - (resistFixed / 100);
            }
            else
                percent = 1 + (caster.Skills.EvalInt.Fixed / 100);

            percent *= 0.01;

            if (percent < 0)
                percent = 0;

            return percent;
        }

        public static int GetOffset(Mobile caster, Mobile target, StatType type, bool curse, bool blockSkill)
        {
            if (Core.AOS)
            {
                if (!blockSkill)
                {
                    //caster.CheckSkill(SkillName.EvalInt, 0.0, 120.0);
                    // This is handled in Spell.cs

                    if (curse)
                        target.CheckSkill(SkillName.MagicResist, 0.0, 120.0);
                }

                double percent = GetOffsetScalar(caster, target, curse);

                switch( type )
                {
                    case StatType.Str:
                        return (int)Math.Ceiling(target.RawStr * percent);
                    case StatType.Dex:
                        return (int)Math.Ceiling(target.RawDex * percent);
                    case StatType.Int:
                        return (int)Math.Ceiling(target.RawInt * percent);
                }
            }

            return 1 + (int)(caster.Skills[SkillName.Magery].Value * 0.1);
        }

        public static Guild GetGuildFor(Mobile m)
        {
            Guild g = m.Guild as Guild;

            if (g == null && m is BaseCreature)
            {
                BaseCreature c = (BaseCreature)m;
                m = c.ControlMaster;

                if (m != null)
                    g = m.Guild as Guild;

                if (g == null)
                {
                    m = c.SummonMaster;

                    if (m != null)
                        g = m.Guild as Guild;
                }
            }

            return g;
        }

        public static bool ValidIndirectTarget(Mobile from, Mobile to)
        {
            if (from == to)
            {
                return true;
            }

            if (to.Hidden && to.AccessLevel > from.AccessLevel)
            {
                return false;
            }

            if (Server.Engines.ArenaSystem.PVPArenaSystem.IsFriendly(from, to))
            {
                return false;
            }

            if (from is BaseCreature && ((BaseCreature)from).GetMaster() != null)
            {
                from = ((BaseCreature)from).GetMaster();
            }

            if (to is BaseCreature && ((BaseCreature)to).GetMaster() != null)
            {
                to = ((BaseCreature)to).GetMaster();
            }

            Guild fromGuild = GetGuildFor(from);
            Guild toGuild = GetGuildFor(to);

            if (fromGuild != null && toGuild != null && (fromGuild == toGuild || fromGuild.IsAlly(toGuild)))
            {
                return false;
            }

            Party p = Party.Get(from);

            if (p != null && p.Contains(to))
            {
                return false;
            }

            if (to is BaseCreature)
            {
                BaseCreature c = (BaseCreature)to;

                if (c.Controlled || c.Summoned)
                {
                    if (c.ControlMaster == from || c.SummonMaster == from)
                    {
                        return false;
                    }

                    if (p != null && (p.Contains(c.ControlMaster) || p.Contains(c.SummonMaster)))
                    {
                        return false;
                    }
                }
            }

            if (from is BaseCreature)
            {
                BaseCreature c = (BaseCreature)from;

                if (c.Controlled || c.Summoned)
                {
                    if (c.ControlMaster == to || c.SummonMaster == to)
                    {
                        return false;
                    }

                    p = Party.Get(to);

                    if (p != null && (p.Contains(c.ControlMaster) || p.Contains(c.SummonMaster)))
                    {
                        return false;
                    }
                }
                else
                {
                    if (to.Player)
                    {
                        return true;
                    }
                    if (to is BaseCreature && (((BaseCreature)to).Controlled || ((BaseCreature)to).Summoned) && ((BaseCreature)to).GetMaster() is PlayerMobile)
                    {
                        return true;
                    }
                }
            }

            // Non-enemy monsters will no longer flag area spells on each other
            if (from is BaseCreature && to is BaseCreature)
            {
                BaseCreature fromBC = (BaseCreature)from;
                BaseCreature toBC = (BaseCreature)to;

                if (fromBC.GetMaster() is BaseCreature)
                    fromBC = fromBC.GetMaster() as BaseCreature;

                if (toBC.GetMaster() is BaseCreature)
                    toBC = toBC.GetMaster() as BaseCreature;

                if (toBC.IsEnemy(fromBC))   //Natural Enemies
                {
                    return true;
                }

                //All involved are monsters- no damage. If falls through this statement, normal noto rules apply
                if (!toBC.Controlled && !toBC.Summoned && !fromBC.Controlled && !fromBC.Summoned) //All involved are monsters- no damage
                {
                    return false;
                }
            }

            if (to is BaseCreature && !((BaseCreature)to).Controlled && ((BaseCreature)to).InitialInnocent)
            {
                return true;
            }

            return (Notoriety.Compute(from, to) != Notoriety.Innocent || from.Murderer);
        }

        public static IEnumerable<IDamageable> AcquireIndirectTargets(Mobile caster, IPoint3D p, Map map, int range)
        {
            return AcquireIndirectTargets(caster, p, map, range, true);
        }

        public static IEnumerable<IDamageable> AcquireIndirectTargets(Mobile caster, IPoint3D p, Map map, int range, bool losCheck)
        {  
            if (map == null)
            {
                yield break;
            }

            IPooledEnumerable eable = map.GetObjectsInRange(new Point3D(p), range);

            foreach (var id in eable.OfType<IDamageable>())
            {
                if (id == caster)
                {
                    continue;
                }

                if (!id.Alive || (losCheck && !caster.InLOS(id)) || !caster.CanBeHarmful(id, false))
                {
                    continue;
                }

                if (id is Mobile && !SpellHelper.ValidIndirectTarget(caster, (Mobile)id))
                {
                    continue;
                }

                yield return id;
            }

            eable.Free();
        }

        private static readonly int[] m_Offsets = new int[]
        {
            -1, -1,
            -1, 0,
            -1, 1,
            0, -1,
            0, 1,
            1, -1,
            1, 0,
            1, 1
        };

        public static void Summon(BaseCreature creature, Mobile caster, int sound, TimeSpan duration, bool scaleDuration, bool scaleStats, bool summoned = true, SkillName useSkill = SkillName.Magery)
        {
            Map map = caster.Map;

            if (map == null)
                return;

            double scale = 1.0 + ((caster.Skills[useSkill].Value - 100.0) / 200.0);

            if (scaleDuration)
                duration = TimeSpan.FromSeconds(duration.TotalSeconds * scale);

            if (scaleStats)
            {
                creature.RawStr = (int)(creature.RawStr * scale);
                creature.Hits = creature.HitsMax;

                creature.RawDex = (int)(creature.RawDex * scale);
                creature.Stam = creature.StamMax;

                creature.RawInt = (int)(creature.RawInt * scale);
                creature.Mana = creature.ManaMax;
            }

            Point3D p = new Point3D(caster);

            if (SpellHelper.FindValidSpawnLocation(map, ref p, true))
            {
                BaseCreature.Summon(creature, summoned, caster, p, sound, duration);
                return;
            }

            /*
            int offset = Utility.Random( 8 ) * 2;

            for( int i = 0; i < m_Offsets.Length; i += 2 )
            {
            int x = caster.X + m_Offsets[(offset + i) % m_Offsets.Length];
            int y = caster.Y + m_Offsets[(offset + i + 1) % m_Offsets.Length];

            if( map.CanSpawnMobile( x, y, caster.Z ) )
            {
            BaseCreature.Summon( creature, caster, new Point3D( x, y, caster.Z ), sound, duration );
            return;
            }
            else
            {
            int z = map.GetAverageZ( x, y );

            if( map.CanSpawnMobile( x, y, z ) )
            {
            BaseCreature.Summon( creature, caster, new Point3D( x, y, z ), sound, duration );
            return;
            }
            }
            }
            * */

            creature.Delete();
            caster.SendLocalizedMessage(501942); // That location is blocked.
        }

        public static bool FindValidSpawnLocation(Map map, ref Point3D p, bool surroundingsOnly)
        {
            if (map == null)	//sanity
                return false;

            if (!surroundingsOnly)
            {
                if (map.CanSpawnMobile(p))	//p's fine.
                {
                    p = new Point3D(p);
                    return true;
                }

                int z = map.GetAverageZ(p.X, p.Y);

                if (map.CanSpawnMobile(p.X, p.Y, z))
                {
                    p = new Point3D(p.X, p.Y, z);
                    return true;
                }
            }

            int offset = Utility.Random(8) * 2;

            for (int i = 0; i < m_Offsets.Length; i += 2)
            {
                int x = p.X + m_Offsets[(offset + i) % m_Offsets.Length];
                int y = p.Y + m_Offsets[(offset + i + 1) % m_Offsets.Length];

                if (map.CanSpawnMobile(x, y, p.Z))
                {
                    p = new Point3D(x, y, p.Z);
                    return true;
                }
                else
                {
                    int z = map.GetAverageZ(x, y);

                    if (map.CanSpawnMobile(x, y, z))
                    {
                        p = new Point3D(x, y, z);
                        return true;
                    }
                }
            }

            return false;
        }

        public static bool RestrictRedTravel { get { return Config.Get("General.RestrictRedsToFel", false); } }

        private delegate bool TravelValidator(Map map, Point3D loc);

        private static readonly TravelValidator[] m_Validators = new TravelValidator[]
        {
            new TravelValidator(IsFeluccaT2A),
            new TravelValidator(IsKhaldun),
            new TravelValidator(IsIlshenar),
            new TravelValidator(IsTrammelWind),
            new TravelValidator(IsFeluccaWind),
            new TravelValidator(IsFeluccaDungeon),
            new TravelValidator(IsTrammelSolenHive),
            new TravelValidator(IsFeluccaSolenHive),
            new TravelValidator(IsCrystalCave),
            new TravelValidator(IsDoomGauntlet),
            new TravelValidator(IsDoomFerry),
            new TravelValidator(IsSafeZone),
            new TravelValidator(IsFactionStronghold),
            new TravelValidator(IsChampionSpawn),
            new TravelValidator(IsTokunoDungeon),
            new TravelValidator(IsLampRoom),
            new TravelValidator(IsGuardianRoom),
            new TravelValidator(IsHeartwood),
            new TravelValidator(IsMLDungeon),
            new TravelValidator(IsSADungeon),
            new TravelValidator(IsTombOfKings),
            new TravelValidator(IsMazeOfDeath),
            new TravelValidator(IsSAEntrance),
            new TravelValidator(IsEodon),
        };

        private static readonly bool[,] m_Rules = new bool[,]
        {
					/*T2A(Fel),	Khaldun,	Ilshenar,	Wind(Tram),	Wind(Fel),	Dungeons(Fel),	Solen(Tram),	Solen(Fel),	CrystalCave(Malas),	Gauntlet(Malas),	Gauntlet(Ferry),	SafeZone,	Stronghold,	ChampionSpawn,	Dungeons(Tokuno[Malas]),	LampRoom(Doom),	GuardianRoom(Doom),	Heartwood,	MLDungeons, SA Dungeons		Tomb of Kings	Maze of Death	SA Entrance,    Eodon*/
/* Recall From */	{ false,	false,		true,		true,		false,		false,			true,			false,		false,				false,				false,				true,		true,		false,			true,						false,			false,				false,		false,      true,           true,           false,          false,          true} ,
/* Recall To */		{ false,	false,		false,		false,		false,		false,			false,			false,		false,				false,				false,				false,		false,		false,			false,						false,			false,				false,		false,      false,          false,          false,          false,          false },
/* Gate From */		{ false,	false,		false,		false,		false,		false,			false,			false,		false,				false,				false,				false,		false,		false,			false,						false,			false,				false,		false,      false,          false,          false,          false,          false },
/* Gate To */		{ false,	false,		false,		false,		false,		false,			false,			false,		false,				false,				false,				false,		false,		false,			false,						false,			false,				false,		false,      false,          false,          false,          false,          false },
/* Mark In */		{ false,	false,		false,		false,		false,		false,			false,			false,		false,				false,				false,				false,		false,		false,			false,						false,			false,				false,		false,      false,          false,          false,          false,          false },
/* Tele From */		{ true,		true,		true,		true,		true,		true,			true,			true,		false,				true,				true,				true,		false,		true,			true,						true,			true,				false,		true,       true,           false,          false,          false,          true },
/* Tele To */		{ true,		true,		true,		true,		true,		true,			true,			true,		false,				true,				false,				false,		false, 		true,			true,						true,			true,				false,		false,      true,           false,          false,          false,          true },
        };

        public static void SendInvalidMessage(Mobile caster, TravelCheckType type)
        {
            if (type == TravelCheckType.RecallTo || type == TravelCheckType.GateTo)
                caster.SendLocalizedMessage(1019004); // You are not allowed to travel there.
            else if (type == TravelCheckType.TeleportTo)
                caster.SendLocalizedMessage(501035); // You cannot teleport from here to the destination.
            else
                caster.SendLocalizedMessage(501802); // Thy spell doth not appear to work...
        }

        public static bool CheckTravel(Mobile caster, TravelCheckType type)
        {
            return CheckTravel(caster, caster.Map, caster.Location, type);
        }

        public static bool CheckTravel(Map map, Point3D loc, TravelCheckType type)
        {
            return CheckTravel(null, map, loc, type);
        }

        private static Mobile m_TravelCaster;
        private static TravelCheckType m_TravelType;

        public static bool CheckTravel(Mobile caster, Map map, Point3D loc, TravelCheckType type)
        {
            if (IsInvalid(map, loc)) // null, internal, out of bounds
            {
                if (caster != null)
                    SendInvalidMessage(caster, type);

                return false;
            }

            if (caster != null)
            {
                if (caster.IsPlayer())
                {
                    // Jail region
                    if (caster.Region.IsPartOf<Regions.Jail>())
                    {
                        caster.SendLocalizedMessage(1114345); // You'll need a better jailbreak plan than that!
                        return false;
                    }
                    else if (caster.Region is Regions.GreenAcres)
                    {
                        caster.SendLocalizedMessage(502360); // You cannot teleport into that area.
                        return false;
                    }
                }

                // Always allow monsters to teleport
                if (caster is BaseCreature && (type == TravelCheckType.TeleportTo || type == TravelCheckType.TeleportFrom))
                {
                    BaseCreature bc = (BaseCreature) caster;

                    if (!bc.Controlled && !bc.Summoned)
                        return true;
                }

                if (Siege.SiegeShard && !Siege.CheckTravel(caster, loc, map, type))
                {
                    return false;
                }
            }

            m_TravelCaster = caster;
            m_TravelType = type;

            int v = (int)type;
            bool isValid = true;

            if (caster != null)
            {
                BaseRegion destination = Region.Find(loc, map) as BaseRegion;
                BaseRegion current = Region.Find(caster.Location, map) as BaseRegion;

                if (destination != null && !destination.CheckTravel(caster, loc, type))
                    isValid = false;

                if (isValid && current != null && !current.CheckTravel(caster, loc, type))
                    isValid = false;

                #region Mondain's Legacy

                if (caster.Region != null)
                {
                    if (caster.Region.IsPartOf("Blighted Grove") && loc.Z < -10)
                        isValid = false;
                }

                if ((int)type <= 4 && (IsNewDungeon(caster.Map, caster.Location) || IsNewDungeon(map, loc)))
                    isValid = false;
                
                #endregion

                #region High Seas

                if (BaseBoat.IsDriving(caster))
                    return false;

                #endregion
            }

            for (int i = 0; isValid && i < m_Validators.Length; ++i)
                isValid = (m_Rules[v, i] || !m_Validators[i](map, loc));

            if (!isValid && caster != null)
                SendInvalidMessage(caster, type);

            return isValid;
        }

        public static bool CheckCanTravel(Mobile m)
        {
            if (Factions.Sigil.ExistsOn(m))
            {
                m.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
                return false;
            }
            else if (m.Criminal)
            {
                m.SendLocalizedMessage(1005561, "", 0x22); // Thou'rt a criminal and cannot escape so easily.
                return false;
            }
            else if (CheckCombat(m))
            {
                m.SendLocalizedMessage(1005564, "", 0x22); // Wouldst thou flee during the heat of battle??
                return false;
            }
            else if (Server.Misc.WeightOverloading.IsOverloaded(m))
            {
                m.SendLocalizedMessage(502359, "", 0x22); // Thou art too encumbered to move.
                return false;
            }

            return true;
        }

        public static bool IsWindLoc(Point3D loc)
        {
            int x = loc.X, y = loc.Y;

            return (x >= 5120 && y >= 0 && x < 5376 && y < 256);
        }

        public static bool IsFeluccaWind(Map map, Point3D loc)
        {
            return (map == Map.Felucca && IsWindLoc(loc));
        }

        public static bool IsTrammelWind(Map map, Point3D loc)
        {
            return (map == Map.Trammel && IsWindLoc(loc));
        }

        public static bool IsIlshenar(Map map, Point3D loc)
        {
            return (map == Map.Ilshenar);
        }

        public static bool IsTrammelSolenHive(Map map, Point3D loc)
        {
            return map == Map.Trammel && Region.Find(loc, map).Name == "Solen Hives";
        }

        public static bool IsFeluccaSolenHive(Map map, Point3D loc)
        {
            return map == Map.Felucca && Region.Find(loc, map).Name == "Solen Hives";
        }

        public static bool IsFeluccaT2A(Map map, Point3D loc)
        {
            int x = loc.X, y = loc.Y;

            return (map == Map.Felucca && x >= 5120 && y >= 2304 && x < 6144 && y < 4096);
        }

        public static bool IsAnyT2A(Map map, Point3D loc)
        {
            int x = loc.X, y = loc.Y;

            return ((map == Map.Trammel || map == Map.Felucca) && x >= 5120 && y >= 2304 && x < 6144 && y < 4096);
        }

        public static bool IsFeluccaDungeon(Map map, Point3D loc)
        {
            Region region = Region.Find(loc, map);
            return (region.IsPartOf<DungeonRegion>() && region.Map == Map.Felucca);
        }

        public static bool IsKhaldun(Map map, Point3D loc)
        {
            return (Region.Find(loc, map).Name == "Khaldun");
        }

        public static bool IsCrystalCave(Map map, Point3D loc)
        {
            if (map != Map.Malas || loc.Z >= -80)
                return false;

            int x = loc.X, y = loc.Y;

            return (x >= 1182 && y >= 437 && x < 1211 && y < 470) ||
                   (x >= 1156 && y >= 470 && x < 1211 && y < 503) ||
                   (x >= 1176 && y >= 503 && x < 1208 && y < 509) ||
                   (x >= 1188 && y >= 509 && x < 1201 && y < 513);
        }

        public static bool IsSafeZone(Map map, Point3D loc)
        {
            return false;
        }

        public static bool IsFactionStronghold(Map map, Point3D loc)
        {
            /*// Teleporting is allowed, but only for faction members
            if ( !Core.AOS && m_TravelCaster != null && (m_TravelType == TravelCheckType.TeleportTo || m_TravelType == TravelCheckType.TeleportFrom) )
            {
            if ( Factions.Faction.Find( m_TravelCaster, true, true ) != null )
            return false;
            }*/
            return (Region.Find(loc, map).IsPartOf<Factions.StrongholdRegion>());
        }

        public static bool IsChampionSpawn(Map map, Point3D loc)
        {
            return (Region.Find(loc, map).IsPartOf<Engines.CannedEvil.ChampionSpawnRegion>());
        }

        public static bool IsDoomFerry(Map map, Point3D loc)
        {
            if (map != Map.Malas)
                return false;

            int x = loc.X, y = loc.Y;

            if (x >= 426 && y >= 314 && x <= 430 && y <= 331)
                return true;

            if (x >= 406 && y >= 247 && x <= 410 && y <= 264)
                return true;

            return false;
        }

        public static bool IsTokunoDungeon(Map map, Point3D loc)
        {
            //The tokuno dungeons are really inside malas
            if (map != Map.Malas)
                return false;

            int x = loc.X, y = loc.Y, z = loc.Z;

            bool r1 = (x >= 0 && y >= 0 && x <= 128 && y <= 128);
            bool r2 = (x >= 45 && y >= 320 && x < 195 && y < 710);

            return (r1 || r2);
        }

        public static bool IsDoomGauntlet(Map map, Point3D loc)
        {
            if (map != Map.Malas)
                return false;

            int x = loc.X - 256, y = loc.Y - 304;

            return (x >= 0 && y >= 0 && x < 256 && y < 256);
        }

        public static bool IsLampRoom(Map map, Point3D loc)
        {
            if (map != Map.Malas)
                return false;

            int x = loc.X, y = loc.Y;

            return (x >= 465 && y >= 92 && x < 474 && y < 102);
        }

        public static bool IsGuardianRoom(Map map, Point3D loc)
        {
            if (map != Map.Malas)
                return false;

            int x = loc.X, y = loc.Y;

            return (x >= 356 && y >= 5 && x < 375 && y < 25);
        }

        public static bool IsHeartwood(Map map, Point3D loc)
        {
            int x = loc.X, y = loc.Y;

            return (map == Map.Trammel || map == Map.Felucca) && (x >= 6911 && y >= 254 && x < 7167 && y < 511);
        }

        public static bool IsMLDungeon(Map map, Point3D loc)
        {
            return MondainsLegacy.IsMLRegion(Region.Find(loc, map));
        }

        public static bool IsTombOfKings(Map map, Point3D loc)
        {
            return (Region.Find(loc, map).IsPartOf(typeof(TombOfKingsRegion)));
        }

        public static bool IsMazeOfDeath(Map map, Point3D loc)
        {
            return (Region.Find(loc, map).IsPartOf(typeof(MazeOfDeathRegion)));
        }

        public static bool IsSAEntrance(Map map, Point3D loc)
        {
            return map == Map.TerMur && loc.X >= 1122 && loc.Y >= 1067 && loc.X <= 1144 && loc.Y <= 1086;
        }

        public static bool IsSADungeon(Map map, Point3D loc)
        {
            if (map != Map.TerMur)
                return false;

            Region region = Region.Find(loc, map);
            return (region.IsPartOf(typeof(DungeonRegion)) && !region.IsPartOf(typeof(TombOfKingsRegion)));
        }

        public static bool IsEodon(Map map, Point3D loc)
        {
            if (map == Map.Felucca && loc.X >= 6975 && loc.X <= 7042 && loc.Y >= 2048 && loc.Y <= 2115)
                return true;

            return map == Map.TerMur && loc.X >= 64 && loc.X <= 1087 && loc.Y >= 1344 && loc.Y <= 2495;
        }

        public static bool IsNewDungeon(Map map, Point3D loc)
        {
            if (map == Map.Trammel && Core.SA)
            {
                Region r = Region.Find(loc, map);

                // Revamped Dungeons with specific rules
                if (r.Name == "Void Pool" || r.Name == "Wrong")
                    return true;
            }

            return false;
        }

        public static bool IsInvalid(Map map, Point3D loc)
        {
            if (map == null || map == Map.Internal)
                return true;

            int x = loc.X, y = loc.Y;

            return (x < 0 || y < 0 || x >= map.Width || y >= map.Height);
        }

        //towns
        public static bool IsTown(IPoint3D loc, Mobile caster)
        {
            if (loc is Item)
                loc = ((Item)loc).GetWorldLocation();

            return IsTown(new Point3D(loc), caster);
        }

        public static bool IsTown(Point3D loc, Mobile caster)
        {
            Map map = caster.Map;

            if (map == null)
                return false;
            
            GuardedRegion reg = (GuardedRegion)Region.Find(loc, map).GetRegion(typeof(GuardedRegion));

            return (reg != null && !reg.IsDisabled());
        }

        public static bool CheckTown(IPoint3D loc, Mobile caster)
        {
            if (loc is Item)
                loc = ((Item)loc).GetWorldLocation();

            return CheckTown(new Point3D(loc), caster);
        }

        public static bool CheckTown(Point3D loc, Mobile caster)
        {
            if (IsTown(loc, caster))
            {
                caster.SendLocalizedMessage(500946); // You cannot cast this in town!
                return false;
            }

            return true;
        }

        //magic reflection
        public static bool CheckReflect(int circle, Mobile caster, ref Mobile target)
        {
            IDamageable c = caster as IDamageable;
            IDamageable t = target as IDamageable;

            bool reflect = CheckReflect(circle, ref c, ref t);

            if (c is Mobile)
                caster = (Mobile)c;

            if (t is Mobile)
                target = (Mobile)t;

            return reflect;
        }

        public static bool CheckReflect(int circle, IDamageable caster, ref Mobile target)
        {
            IDamageable t = target as IDamageable;

            bool reflect = CheckReflect(circle, ref caster, ref t);

            if (t is Mobile)
                caster = (Mobile)t;

            return reflect;
        }

        public static bool CheckReflect(int circle, Mobile caster, ref IDamageable target)
        {
            IDamageable c = caster as IDamageable;

            bool reflect = CheckReflect(circle, ref c, ref target);

            if (c is Mobile)
                caster = (Mobile)c;

            return reflect;
        }

        public static bool CheckReflect(int circle, ref Mobile caster, ref IDamageable target, DamageType type = DamageType.Spell)
        {
            IDamageable c = caster as IDamageable;

            bool reflect = CheckReflect(circle, ref c, ref target);

            if (c is Mobile)
                caster = (Mobile)c;

            return reflect;
        }

        public static bool CheckReflect(int circle, ref Mobile caster, ref Mobile target)
        {
            return CheckReflect(circle, caster, ref target);
        }

        public static bool CheckReflect(int circle, ref IDamageable source, ref IDamageable defender, DamageType type = DamageType.Spell)
        {
            bool reflect = false;
            Mobile target = defender as Mobile;

            if (Core.AOS && type >= DamageType.Spell)
            {
                if (target != null && defender is Mobile)
                {
                    Clone clone = MirrorImage.GetDeflect(target, (Mobile)defender);

                    if (clone != null)
                    {
                        defender = clone;
                        return false;
                    }
                }
                else if (defender is DamageableItem && ((DamageableItem)defender).CheckReflect(circle, source))
                {
                    IDamageable temp = source;
                    source = defender;
                    defender = temp;
                    return true;
                }
            }

            Mobile caster = source as Mobile;

            if (target == null || caster == null)
                return false;

            if (target.MagicDamageAbsorb > 0)
            {
                ++circle;

                target.MagicDamageAbsorb -= circle;

                // This order isn't very intuitive, but you have to nullify reflect before target gets switched

                reflect = (target.MagicDamageAbsorb >= 0);

                if (target is BaseCreature)
                    ((BaseCreature)target).CheckReflect(caster, ref reflect);

                if (target.MagicDamageAbsorb <= 0)
                {
                    target.MagicDamageAbsorb = 0;
                    DefensiveSpell.Nullify(target);
                }

                if (reflect)
                {
                    target.FixedEffect(0x37B9, 10, 5);

                    Mobile temp = caster;
                    source = target;
                    target = temp;
                }
            }
            else if (target is BaseCreature)
            {
                reflect = false;

                ((BaseCreature)target).CheckReflect(caster, ref reflect);

                if (reflect)
                {
                    target.FixedEffect(0x37B9, 10, 5);

                    IDamageable temp = source;
                    source = defender;
                    defender = temp;
                }
            }

            return reflect;
        }

        public static void Damage(Spell spell, Mobile target, double damage)
        {
            TimeSpan ts = GetDamageDelayForSpell(spell);

            Damage(spell, ts, target, spell.Caster, damage);
        }

        public static void Damage(TimeSpan delay, Mobile target, double damage)
        {
            Damage(delay, target, null, damage);
        }

        public static void Damage(TimeSpan delay, Mobile target, Mobile from, double damage)
        {
            Damage(null, delay, target, from, damage);
        }

        public static void Damage(Spell spell, TimeSpan delay, Mobile target, Mobile from, double damage)
        {
            int iDamage = (int)damage;

            if (delay == TimeSpan.Zero)
            {
                if (from is BaseCreature)
                    ((BaseCreature)from).AlterSpellDamageTo(target, ref iDamage);

                if (target is BaseCreature)
                    ((BaseCreature)target).AlterSpellDamageFrom(from, ref iDamage);

                target.Damage(iDamage, from);
            }
            else
            {
                new SpellDamageTimer(spell, target, from, iDamage, delay).Start();
            }

            if (target is BaseCreature && from != null && delay == TimeSpan.Zero)
            {
                BaseCreature c = (BaseCreature)target;

                c.OnHarmfulSpell(from);
                c.OnDamagedBySpell(from);
            }
        }

        public static void Damage(Spell spell, IDamageable damageable, double damage, int phys, int fire, int cold, int pois, int nrgy)
        {
            TimeSpan ts = GetDamageDelayForSpell(spell);

            Damage(spell, ts, damageable, spell.Caster, damage, phys, fire, cold, pois, nrgy, DFAlgorithm.Standard);
        }

        public static void Damage(Spell spell, IDamageable damageable, double damage, int phys, int fire, int cold, int pois, int nrgy, DFAlgorithm dfa)
        {
            TimeSpan ts = GetDamageDelayForSpell(spell);

            Damage(spell, ts, damageable, spell.Caster, damage, phys, fire, cold, pois, nrgy, dfa);
        }

        public static void Damage(Spell spell, IDamageable damageable, double damage, int phys, int fire, int cold, int pois, int nrgy, int chaos, int direct)
        {
            TimeSpan ts = GetDamageDelayForSpell(spell);

            Damage(spell, ts, damageable, spell.Caster, damage, phys, fire, cold, pois, nrgy, DFAlgorithm.Standard, chaos, direct);
        }

        public static void Damage(TimeSpan delay, IDamageable damageable, double damage, int phys, int fire, int cold, int pois, int nrgy)
        {
            Damage(delay, damageable, null, damage, phys, fire, cold, pois, nrgy);
        }

        public static void Damage(TimeSpan delay, IDamageable damageable, double damage, int phys, int fire, int cold, int pois, int nrgy, int chaos, int direct)
        {
            Damage(null, delay, damageable, null, damage, phys, fire, cold, pois, nrgy, DFAlgorithm.Standard, chaos, direct);
        }

        public static void Damage(TimeSpan delay, IDamageable damageable, Mobile from, double damage, int phys, int fire, int cold, int pois, int nrgy)
        {
            Damage(delay, damageable, from, damage, phys, fire, cold, pois, nrgy, DFAlgorithm.Standard);
        }

        public static void Damage(TimeSpan delay, IDamageable damageable, Mobile from, double damage, int phys, int fire, int cold, int pois, int nrgy, DFAlgorithm dfa)
        {
            Damage(null, delay, damageable, from, damage, phys, fire, cold, pois, nrgy, dfa);
        }

        public static void Damage(Spell spell, TimeSpan delay, IDamageable damageable, Mobile from, double damage, int phys, int fire, int cold, int pois, int nrgy, DFAlgorithm dfa, int chaos = 0, int direct = 0)
        {
            Mobile target = damageable as Mobile;
            int iDamage = (int)damage;

            if (delay == TimeSpan.Zero)
            {
                if (from is BaseCreature && target != null)
                    ((BaseCreature)from).AlterSpellDamageTo(target, ref iDamage);

                if (target is BaseCreature)
                    ((BaseCreature)target).AlterSpellDamageFrom(from, ref iDamage);

                DamageType dtype = spell != null ? spell.SpellDamageType : DamageType.Spell;

                if (target != null)
                {
                    target.DFA = dfa;
                }

                int damageGiven = AOS.Damage(damageable, from, iDamage, phys, fire, cold, pois, nrgy, chaos, direct, dtype);

                if(target != null)
                    Spells.Mysticism.SpellPlagueSpell.OnMobileDamaged(target);

                if (target != null && target.DFA != DFAlgorithm.Standard)
                {
                    target.DFA = DFAlgorithm.Standard;
                }

                NegativeAttributes.OnCombatAction(from);

                if(from != target)
                    NegativeAttributes.OnCombatAction(target);
            }
            else
            {
                new SpellDamageTimerAOS(spell, damageable, from, iDamage, phys, fire, cold, pois, nrgy, chaos, direct, delay, dfa).Start();
            }

            if (target is BaseCreature && from != null && delay == TimeSpan.Zero)
            {
                BaseCreature c = (BaseCreature)target;

                c.OnHarmfulSpell(from);
                c.OnDamagedBySpell(from);
            }
        }

        public static void Heal(int amount, Mobile target, Mobile from)
        {
            Heal(amount, target, from, true);
        }

        public static void Heal(int amount, Mobile target, Mobile from, bool message)
        {
            Spellweaving.ArcaneEmpowermentSpell.AddHealBonus(from, ref amount);

            if (amount > 0 && target != from && from is PlayerMobile && target is PlayerMobile)
            {
                if (SearingWounds.IsUnderEffects(target))
                {
                    amount /= 2;
                    target.SendLocalizedMessage(1151178); // The cauterized wound resists some of your healing.
                }

                int realAmount = Math.Min(amount, target.HitsMax - target.Hits);

                if (realAmount > 0 && target != from)
                    SpiritualityVirtue.OnHeal(from, realAmount);
            }

            target.Heal(amount, from, message);
        }

        private class SpellDamageTimer : Timer
        {
            private readonly Mobile m_Target;

            private readonly Mobile m_From;

            private int m_Damage;
            private readonly Spell m_Spell;

            public SpellDamageTimer(Spell s, Mobile target, Mobile from, int damage, TimeSpan delay)
                : base(delay)
            {
                m_Target = target;
                m_From = from;
                m_Damage = damage;
                m_Spell = s;

                if (m_Spell != null && m_Spell.DelayedDamage && !m_Spell.DelayedDamageStacking)
                    m_Spell.StartDelayedDamageContext(target, this);

                Priority = TimerPriority.TwentyFiveMS;
            }

            protected override void OnTick()
            {
                if (m_From is BaseCreature)
                    ((BaseCreature)m_From).AlterSpellDamageTo(m_Target, ref m_Damage);

                if (m_Target is BaseCreature)
                    ((BaseCreature)m_Target).AlterSpellDamageFrom(m_From, ref m_Damage);

                m_Target.Damage(m_Damage);
                if (m_Spell != null)
                    m_Spell.RemoveDelayedDamageContext(m_Target);
            }
        }

        public class SpellDamageTimerAOS : Timer
        {
            private IDamageable m_Target;
            private readonly Mobile m_From;
            private int m_Damage;
            private int m_Phys;
            private int m_Fire;
            private int m_Cold;
            private int m_Pois;
            private int m_Nrgy;
            private int m_Chaos;
            private int m_Direct;
            private DFAlgorithm m_DFA;
            private Spell m_Spell;

            public Spell Spell { get { return m_Spell; } }

            public SpellDamageTimerAOS(Spell s, IDamageable target, Mobile from, int damage, int phys, int fire, int cold, int pois, int nrgy, int chaos, int direct, TimeSpan delay, DFAlgorithm dfa)
                : base(delay)
            {
                m_Target = target;
                m_From = from;
                m_Damage = damage;
                m_Phys = phys;
                m_Fire = fire;
                m_Cold = cold;
                m_Pois = pois;
                m_Nrgy = nrgy;
                m_Chaos = chaos;
                m_Direct = direct;
                m_DFA = dfa;
                m_Spell = s;

                if (m_Spell != null && m_Spell.DelayedDamage && !m_Spell.DelayedDamageStacking)
                    m_Spell.StartDelayedDamageContext(target, this);

                Priority = TimerPriority.TwentyFiveMS;
            }

            protected override void OnTick()
            {
                Mobile target = m_Target as Mobile;

                if (m_From is BaseCreature && target != null)
                    ((BaseCreature)m_From).AlterSpellDamageTo(target, ref m_Damage);

                if (m_Target is BaseCreature && m_From != null)
                    ((BaseCreature)m_Target).AlterSpellDamageFrom(m_From, ref m_Damage);

                DamageType dtype = m_Spell != null ? m_Spell.SpellDamageType : DamageType.Spell;

                if (target != null)
                {
                    target.DFA = m_DFA;
                }

                int damageGiven = AOS.Damage(m_Target, m_From, m_Damage, m_Phys, m_Fire, m_Cold, m_Pois, m_Nrgy, m_Chaos, m_Direct, dtype);

                if (target != null && target.DFA != DFAlgorithm.Standard)
                {
                    target.DFA = DFAlgorithm.Standard;
                }

                if (m_Target is BaseCreature && m_From != null)
                {
                    BaseCreature c = (BaseCreature)m_Target;

                    c.OnHarmfulSpell(m_From);
                    c.OnDamagedBySpell(m_From);
                }

                if (target != null)
                    Spells.Mysticism.SpellPlagueSpell.OnMobileDamaged(target);

                if (m_Spell != null)
                    m_Spell.RemoveDelayedDamageContext(m_Target);

                NegativeAttributes.OnCombatAction(m_From);

                if (m_From != target)
                    NegativeAttributes.OnCombatAction(target);
            }
        }
    }

    public class TransformationSpellHelper
    {
        #region Context Stuff
        private static readonly Dictionary<Mobile, TransformContext> m_Table = new Dictionary<Mobile, TransformContext>();

        public static void AddContext(Mobile m, TransformContext context)
        {
            m_Table[m] = context;
        }

        public static void RemoveContext(Mobile m, bool resetGraphics)
        {
            TransformContext context = GetContext(m);

            if (context != null)
                RemoveContext(m, context, resetGraphics);
        }

        public static void RemoveContext(Mobile m, TransformContext context, bool resetGraphics)
        {
            if (m_Table.ContainsKey(m))
            {
                m_Table.Remove(m);

                List<ResistanceMod> mods = context.Mods;

                for (int i = 0; i < mods.Count; ++i)
                    m.RemoveResistanceMod(mods[i]);

                if (resetGraphics)
                {
                    m.HueMod = -1;
                    m.BodyMod = 0;
                }

                context.Timer.Stop();
                context.Spell.RemoveEffect(m);
            }
        }

        public static TransformContext GetContext(Mobile m)
        {
            TransformContext context = null;

            m_Table.TryGetValue(m, out context);

            return context;
        }

        public static bool UnderTransformation(Mobile m)
        {
            return (GetContext(m) != null);
        }

        public static bool UnderTransformation(Mobile m, Type type)
        {
            TransformContext context = GetContext(m);

            return (context != null && context.Type == type);
        }

        public static void CheckCastSkill(Mobile m, TransformContext context)
        {
            Spell spell = context.Spell as Spell;

            if (spell != null)
            {
                double min, max;

                spell.GetCastSkills(out min, out max);

                if (m.Skills[spell.CastSkill].Value < min)
                {
                    RemoveContext(m, context, true);
                }
            }
        }

        #endregion

        public static bool CheckCast(Mobile caster, Spell spell)
        {
            if (Factions.Sigil.ExistsOn(caster))
            {
                caster.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
                return false;
            }
            else if (!caster.CanBeginAction(typeof(PolymorphSpell)))
            {
                caster.SendLocalizedMessage(1061628); // You can't do that while polymorphed.
                return false;
            }
            else if (AnimalForm.UnderTransformation(caster))
            {
                caster.SendLocalizedMessage(1061091); // You cannot cast that spell in this form.
                return false;
            }
			else if (caster.Flying && !(spell is VampiricEmbraceSpell))
			{
				caster.SendLocalizedMessage(1112567); // You are flying.
				return false;
			}

            return true;
        }

        public static bool OnCast(Mobile caster, Spell spell)
        {
            ITransformationSpell transformSpell = spell as ITransformationSpell;

            if (transformSpell == null)
                return false;

            if (Factions.Sigil.ExistsOn(caster))
            {
                caster.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
            }
            else if (!caster.CanBeginAction(typeof(PolymorphSpell)))
            {
                caster.SendLocalizedMessage(1061628); // You can't do that while polymorphed.
            }
            else if (DisguiseTimers.IsDisguised(caster))
            {
                caster.SendLocalizedMessage(1061631); // You can't do that while disguised.
                return false;
            }
            else if (AnimalForm.UnderTransformation(caster))
            {
                caster.SendLocalizedMessage(1061091); // You cannot cast that spell in this form.
            }
            else if (!caster.CanBeginAction(typeof(IncognitoSpell)) || (caster.IsBodyMod && GetContext(caster) == null))
            {
                spell.DoFizzle();
            }
            else if (spell.CheckSequence())
            {
                TransformContext context = GetContext(caster);
                Type ourType = spell.GetType();

                bool wasTransformed = (context != null);
                bool ourTransform = (wasTransformed && context.Type == ourType);

                if (wasTransformed)
                {
                    RemoveContext(caster, context, ourTransform);

                    if (ourTransform)
                    {
                        caster.PlaySound(0xFA);
                        caster.FixedParticles(0x3728, 1, 13, 5042, EffectLayer.Waist);
                    }
                }

                if (!ourTransform)
                {
                    List<ResistanceMod> mods = new List<ResistanceMod>();

                    if (transformSpell.PhysResistOffset != 0)
                        mods.Add(new ResistanceMod(ResistanceType.Physical, transformSpell.PhysResistOffset));

                    if (transformSpell.FireResistOffset != 0)
                        mods.Add(new ResistanceMod(ResistanceType.Fire, transformSpell.FireResistOffset));

                    if (transformSpell.ColdResistOffset != 0)
                        mods.Add(new ResistanceMod(ResistanceType.Cold, transformSpell.ColdResistOffset));

                    if (transformSpell.PoisResistOffset != 0)
                        mods.Add(new ResistanceMod(ResistanceType.Poison, transformSpell.PoisResistOffset));

                    if (transformSpell.NrgyResistOffset != 0)
                        mods.Add(new ResistanceMod(ResistanceType.Energy, transformSpell.NrgyResistOffset));

                    if (!((Body)transformSpell.Body).IsHuman)
                    {
                        Mobiles.IMount mt = caster.Mount;

                        if (mt != null)
                            mt.Rider = null;
                    }

                    caster.BodyMod = transformSpell.Body;
                    caster.HueMod = transformSpell.Hue;

                    for (int i = 0; i < mods.Count; ++i)
                        caster.AddResistanceMod(mods[i]);

                    transformSpell.DoEffect(caster);

                    Timer timer = new TransformTimer(caster, transformSpell);
                    timer.Start();

                    AddContext(caster, new TransformContext(timer, mods, ourType, transformSpell));
                    return true;
                }
            }

            return false;
        }
    }

    public interface ITransformationSpell
    {
        int Body { get; }
        int Hue { get; }

        int PhysResistOffset { get; }
        int FireResistOffset { get; }
        int ColdResistOffset { get; }
        int PoisResistOffset { get; }
        int NrgyResistOffset { get; }

        double TickRate { get; }
        void OnTick(Mobile m);

        void DoEffect(Mobile m);

        void RemoveEffect(Mobile m);
    }

    public class TransformContext
    {
        private readonly Timer m_Timer;
        private readonly List<ResistanceMod> m_Mods;
        private readonly Type m_Type;
        private readonly ITransformationSpell m_Spell;

        public Timer Timer
        {
            get
            {
                return m_Timer;
            }
        }
        public List<ResistanceMod> Mods
        {
            get
            {
                return m_Mods;
            }
        }
        public Type Type
        {
            get
            {
                return m_Type;
            }
        }
        public ITransformationSpell Spell
        {
            get
            {
                return m_Spell;
            }
        }

        public TransformContext(Timer timer, List<ResistanceMod> mods, Type type, ITransformationSpell spell)
        {
            m_Timer = timer;
            m_Mods = mods;
            m_Type = type;
            m_Spell = spell;
        }
    }

    public class TransformTimer : Timer
    {
        private readonly Mobile m_Mobile;
        private readonly ITransformationSpell m_Spell;

        public TransformTimer(Mobile from, ITransformationSpell spell)
            : base(TimeSpan.FromSeconds(spell.TickRate), TimeSpan.FromSeconds(spell.TickRate))
        {
            m_Mobile = from;
            m_Spell = spell;

            Priority = TimerPriority.TwoFiftyMS;
        }

        protected override void OnTick()
        {
            if (m_Mobile.Deleted || !m_Mobile.Alive || m_Mobile.Body != m_Spell.Body || (m_Mobile.Hue != m_Spell.Hue && !BestialSetHelper.IsBerserk(m_Mobile)))
            {
                TransformationSpellHelper.RemoveContext(m_Mobile, true);
                Stop();
            }
            else
            {
                m_Spell.OnTick(m_Mobile);
            }
        }
    }
}
